#include "simulation.h"

Simulation::Simulation(int argc, char **argv) {


    avg_service_time = 0;
    avg_waiting_time = 0;
    avg_system_time = 0;	

    kSeed_arrival = time(NULL);				//seed for the generating the interarrival times
    kSeed_service = time(NULL) + 10467;		//seed for the generating the service times

    //command line inputs and validations
    lambda = atof(argv[1]);		//0 < lambda < 1
    if (lambda > 1) {
        cout << "Invalid value of lambda. lambda must be in (0, 1)" << endl;
        return;
    }
    mu = 1.0 / 3000;						//mu = 1 always

    c = atoi(argv[2]);
    if (c <= 0) {
        cout << "Invalid value of C. C must be greater than 0" << endl;
        return;
    }
    l = atoi(argv[3]);
    if (! (l == 0 || l == 1)) {
        cout << "Invalid value of L" << endl;
        exit(0);
    }
    m = atoi(argv[4]);
    if (! (m == 0 || m == 1)) {
        cout << "Invalid value of M" << endl;
        exit(0);
    }
    type = atoi(argv[5]);
    if (type == 0) {
        numservers = 1;
    } else if (type == 1) {
        numservers = 3;
    }
    Ta = 200.0;
    Ts = 100.0;
    te = 1<<30;
    n = 0;
    t1 = expntl(1/lambda, kSeed_arrival);
    t2 = te;
    sim_time = 0.0;	

    serve_count = 0;
    master_clock = 0;

    arrival_count = 0;
    departure_count = 0;
    total_arrival_count = 0;
    total_departure_count = 0;

    //initiate the arrival of the first customer, generate the corresponding event,
    //add it to the event list and add the customer to the customer_list
    //set simulation time
    Ts = expntl(1/mu, kSeed_service);
    Customer *customer = new Customer(total_arrival_count++, t1, Ts, 0.0, 0.0, false, false);
    EventNode *next_event = new EventNode('A', customer, t1);
    event_list_add(next_event);
    sim_time = t1;
    customer_list.push_back(customer);
}

//function to generate the random number in the range (0, 1), given a seed
double Simulation::ran0(long *idnum) {
    long k;      
    double ans;
    *idnum ^= MASK;
    k = (*idnum)/IQ;
    *idnum = IA * (*idnum - k * IQ) - IR * k;
    if (*idnum < 0) {
        *idnum += IM;
    }
    ans = AM * (*idnum);
    *idnum ^= MASK;
    return ans;
}

//function to generate bounded pareto distributed numbers with parameters, k, p, and alpha
double Simulation::pareto(long *idnum) {
    double dummy;
    do {
        dummy = ran0(idnum);
    } while (dummy == 0.0);
    return 1.0 * 332 / pow(1 - dummy * (1 - pow(1.0 * 332 / 10000000000, 1.1)), 1/1.1); 
}

//function to generate an exponentially distributed random number with mean 1
double Simulation::expdev(long *idnum) {
    double dummy;
    do {
        dummy = ran0(idnum);
    } while (dummy == 0.0);
    return -log(dummy);
}

//function to generate an exponentially distributed random number with mean "mean"
double Simulation::expntl(double mean, long &seed) {
    return 1.0 * mean * Simulation::expdev(&seed);
}

Simulation::EventNode *Simulation::find_event_for_customer(Customer *customer, char eventType) {
    for (uint32_t index = 0; index < event_list.size(); ++index) {
        if (event_list[index]->type_ == eventType &&
                event_list[index]->customer_ == customer) {
            return event_list[index];	
        }
    }
    return NULL;
}

bool Simulation::event_list_empty() {
    return event_list.empty();
}

//returns the current size of the event list
uint32_t Simulation::event_list_size() {
    return event_list.size();
}

//returns the first element (i.e. the next event) from the event list
Simulation::EventNode *Simulation::event_list_front() {
    if (! event_list_empty()) {
        return event_list[0];
    }
    return NULL;	
}

//adds a new element (i.e. a new event) to the event list, ordered by the time of the event
void Simulation::event_list_add(EventNode *eventNode) {
    if (event_list_empty()) {
        event_list.push_back(eventNode);
        return;
    } else if (event_list_size() == 1) {
        if (event_list[0]->time_ <= eventNode->time_) {
            event_list.push_back(eventNode);
        } else {
            event_list.insert(event_list.begin(), eventNode);
        } 
    } else {
        uint32_t count = 0;
        while (event_list[count]->time_ <= eventNode->time_) { 
            count++;
            if (count == event_list_size()) {
                break;
            }
        }
        if (count < event_list_size()) {
            event_list.insert(event_list.begin() + count, eventNode);
        } else {
            event_list.push_back(eventNode);
        }
    }
    //event_list_print();
}

Simulation::EventNode *Simulation::event_list_remove(EventNode *eventNode) {
    EventNode *event = NULL;
    for (uint32_t index = 0; index < event_list.size(); ++index) {
        if (event_list[index]->type_ == eventNode->type_ &&
                event_list[index]->customer_ == eventNode->customer_ &&
                event_list[index]->time_ == eventNode->time_) {
            event = event_list[index];
            event_list.erase(event_list.begin() + index);
            break;
        }
    }
    return event;
}

//removes an element (i.e. the next event) from the event list
Simulation::EventNode *Simulation::event_list_remove_front() {
    if (event_list_empty()) {
        return NULL;
    }
    EventNode *event = event_list_front();
    event_list.erase(event_list.begin());
    return event;
}

//prints the contents of the event list
void Simulation::event_list_print() {
    cout << "\nSTART\n";
    for (uint32_t index = 0; index < event_list.size(); ++index) {
        event_list[index]->print();
    }
    cout << "\nEND\n";
}

void Simulation::calculate_statistics() {
    //double avg_service_time = 0, avg_waiting_time = 0, avg_system_time = 0;
    uint32_t customers_lost = 0, customers_serviced = 0;
    for (uint32_t index = 0; index < customer_list.size(); ++index) {
        customer_list[index]->waiting_time_ = customer_list[index]->departure_time_ - 
            customer_list[index]->service_time_ -
            customer_list[index]->arrival_time_;
        if (customer_list[index]->waiting_time_ < 0) {
            customer_list[index]->waiting_time_ = 0;
        }
        //	if (customer_list[index]->serviced_ && customer_list[index]->departure_number_ > 1000) {
        if (type == 0) {
            if (customer_list[index]->departure_number_ > 0) {		
                customers_serviced++;
                customer_list[index]->slowdown_ = 1.0 * customer_list[index]->waiting_time_ / customer_list[index]->service_time_;
                avg_service_time += customer_list[index]->service_time_;
                avg_waiting_time += customer_list[index]->waiting_time_;
                avg_system_time += (customer_list[index]->departure_time_ -
                        customer_list[index]->arrival_time_);
                if (type == 0) {
                    cout << customer_list[index]->service_time_ << " " << customer_list[index]->slowdown_ << " " 
                        << customer_list[index]->waiting_time_ << endl;
                }
                if (customer_list[index]->lost_) {
                    customers_lost++;
                }
            }
        } else if (type == 1) {
            if (customer_list[index]->departure_number_ > TRANSIENT_COUNT) {		
                customers_serviced++;
                customer_list[index]->slowdown_ = 1.0 * customer_list[index]->waiting_time_ / customer_list[index]->service_time_;
                avg_service_time += customer_list[index]->service_time_;
                avg_waiting_time += customer_list[index]->waiting_time_;
                avg_system_time += (customer_list[index]->departure_time_ -
                        customer_list[index]->arrival_time_);
                if (type == 0) {
                    cout << customer_list[index]->service_time_ << " " << customer_list[index]->slowdown_ << " " 
                        << customer_list[index]->waiting_time_ << endl;
                }
            } 
            if (customer_list[index]->lost_) {
                customers_lost++;
            }
        }
        //customer_list[index]->print();
    }
    avg_service_time /= (c - TRANSIENT_COUNT);
    avg_waiting_time /= (c - TRANSIENT_COUNT);
    avg_system_time /= (c - TRANSIENT_COUNT);
    clr = 1.0 * (total_arrival_count - c) / (total_arrival_count - TRANSIENT_COUNT);
    //avg_service_time /= customers_serviced;
    //avg_waiting_time /= customers_serviced;
    }

    void Simulation::customer_diagnostics(uint32_t index) {
        if (index == 0 || index > customer_list.size()) {
            cout << "simtime in arrival = " << sim_time << endl;
            cout << "Specified customer ID out of range" << endl;
            return;
        }
        cout << "Customer " << index << endl;
        if (customer_list[index - 1]->serviced_) {
            cout << "Arrival time = " << customer_list[index - 1]->arrival_time_ << endl;
            cout << "Service time = " << customer_list[index - 1]->service_time_ << endl;
            cout << "Departure time = " << customer_list[index - 1]->departure_time_ << endl;
            cout << "Number of customers in the system after the departure = " << customer_list[index - 1]->customers_after_ << endl;
        } else {
            cout << "Not serviced. Setting departure time = arrival time" << endl;
            customer_list[index - 1]->departure_time_ = customer_list[index - 1]->arrival_time_;
        }
    }

    void Simulation::run() {
        while (serve_count < c) {
            //this->print_queues();
            //event_list_print();
            EventNode *event = event_list_remove_front();
            if (event != NULL) {
                if (event->type_ == 'A') {
                    this->process_arrival(event);
                } else if (event->type_ == 'D') {
                    this->process_departure(event);
                } 
            } else {
                Ta = expntl(1/lambda, kSeed_arrival);
                Customer *customer = new Customer(total_arrival_count++, sim_time + Ta, 0.0, 0.0, 0.0, false, false);
                EventNode *next_event = new EventNode('A', customer, sim_time + Ta);
                event_list_add(next_event);
                customer_list.push_back(customer);
            }
            //cout << "serve count = " << serve_count << endl;
        }
        total_arrival_count--;
    }
